viewChild
函數的另一個高階用例是將 NgTemplate
嵌入到 ViewConatinerRef
中。當範本非常簡單到擁有一個組件就顯得有些過分時,我們可以透過程式設計方式附加一個 NgTemplate
而不是組件。嵌入 NgTemplat
e 的工作類似於建立動態組件。
我將重複第 18 天完成的示範,但這一次,App
組件附加 NgTemplate
而不是 StarWarCharacterComponent
。此示範使用 viewChild
函數來查詢 ViewContainerRef
並呼叫 createEmbeddedView
方法。 createEmbeddedView
方法接受 TemplateRef 和選擇性的 template context。
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
export const appConfig = {
providers: [
provideHttpClient(),
provideExperimentalZonelessChangeDetection()
]
}
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app.config';
bootstrapApplication(App, appConfig);
提供 Http client 和 experiental zoneless feature,並引導應用程式設定。
import { catchError, map, of, mergeMap, forkJoin } from 'rxjs';
import { inject, runInInjectionContext, Injector } from '@angular/core';
import { HttpClient } from '@angular/common/http';
export type Person = {
name: string;
height: string;
mass: string;
hair_color: string;
skin_color: string;
eye_color: string;
gender: string;
films: string[];
}
const URL = 'https://swapi.dev/api/people';
export function getPerson(id: number, injector: Injector) {
return runInInjectionContext(injector, () => {
const http = inject(HttpClient);
return http.get<Person>(`${URL}/${id}`).pipe(
catchError((err) => {
console.error(err);
return of(undefined);
}));
});
}
getPerson
函數透過 id
檢索星際大戰角色。`
<ng-template #starWar let-id let-person="person" let-isSith="isSith">
<div class="border">
@if(person) {
<p>Id: {{ id }} </p>
@if (isSith) {
<p>Is a Sith. He is evil.</p>
}
<p>Name: {{ person.name }}</p>
<p>Height: {{ person.height }}</p>
<p>Mass: {{ person.mass }}</p>
<p>Hair Color: {{ person.hair_color }}</p>
<p>Skin Color: {{ person.skin_color }}</p>
<p>Eye Color: {{ person.eye_color }}</p>
<p>Gender: {{ person.gender }}</p>
} @else {
<p>No info</p>
}
</div>
</ng-template>
ngTemplate
有一個範本變數 (template variable) starWar、一個隱式變數 (let-id) 和兩個命名變數(let-person 和 let-isSith)。 let-person
綁定到 person
屬性,而 let-isSith
則綁定到 isSith
屬性。 此範本顯示星際大戰角色的 id
和詳細資訊。 如果 isSith
為 true
,範本將顯示文字 "Is a Sith, He is evil."。
Component({
selector: 'app-root',
standalone: true,
imports: [FormsModule],
template: `
<div class="container">
<ng-container #vcr />
</div>
<select [(ngModel)]="jediId">
<option value="1">Luke</option>
<option value="10">Obi Wan Kenobe</option>
<option value="20">Yoda</option>
</select>
<button (click)="addAJedi(jediId())">Add a Jedi</button>
<select [(ngModel)]="sithId">
<option value="4">Darth Vader</option>
<option value="44">Darth Maul</option>
</select>
<button (click)="addAJedi(sithId(), true)">Add a Sith</button>`,
<ng-template #starWar>...</ng-template>`,
})
export class App implements OnDestroy {
jediId = signal(1);
sithId = signal(4);
ngOnDestroy(): void {}
}
應用程式組件由 Jedi
和 Sith
下拉列表組成。 Jedi
列表的 NgModel
綁定到 jediId
signal,Sith
列表的 NgModel
綁定到 sithId
signal。當使用者按一下 "Add a Jedi" 按鈕時,該元件會呼叫 addAJedi
方法將範本嵌入到 ViewContainerRef
中。同樣,使用者點擊 "Add a Sith" 按鈕來呼叫相同的方法將範本嵌入到 ViewContainerRef
中。
<ng-container #vcr />
NgContainer 有一個範本變數 vcr
,viewChild
函數使用它來查詢 ViewContainerRef
。
vcr = viewChild.required('vcr', { read: ViewContainerRef });
vcr
的類型是 Signal<ViewContainerRef>
,因為 read
屬性會擷取 ViewContainerRef
。
templateRef = viewChild.required('starWar', { read: TemplateRef });
viewChild
函數查詢 starWar
並檢索 TemplateRef
。
embeddedViewRefs = [] as EmbeddedViewRef<any>[];
async addAJedi(id: number, isSith = false) {
const person = await lastValueFrom(getPerson(id, this.injector));
const context = {
$implicit: id,
isSith,
person,
};
const embeddedViewRef = this.vcr().createEmbeddedView(this.templateRef(), context);
this.embeddedViewRefs.push(embeddedViewRef);
}
addJedi
方法呼叫 getPerson
函數並使用 RxJS 運算子 lastValueFrom
來檢索 Star War 角色。 然後,context
儲存隱式屬性和命名屬性。 createEmbeddedView
方法將 TemplateRef
附加到 ViewContainerRer
並傳回 embeddedViewRef
。 將 embeddedViewRef
附加到要在ngDestroy lifecycle hook 中銷毀 embeddedViewRefs
陣列。
ngOnDestroy(): void {
if (this.embeddedViewRefs) {
for (const ref of this.embeddedViewRefs) {
ref.destroy();
}
}
}
當應用程式銷毀 App
組件時,ngOnDestroy
會釋放 embeddedViewRefs
的 memory 以避免 memory leak。
viewChild
可以查詢 ViewContainerRef
,並且 ViewContainerRef
可以呼叫 createEmbeddedView
方法以程式設計方式附加 ngTemplate
。createEmbddedView
接受 TemplateRef
和 template context, ngTemplate
可以存取屬性以顯示其值。NgTemplate
。否則,請使用 Angular 組件。鐵人賽的第 19 天就這樣結束了。